Imports System
Imports System.Collections.Generic
Imports System.Text
Imports System.Diagnostics
Imports System.Runtime.InteropServices
Imports System.Threading
Imports System.ComponentModel

Namespace Components

    <DefaultPropertyAttribute("IdleThreshold")> _
    <DefaultEventAttribute("UserActiveEvent")> _
    Public Class UserActivityTimer
        Inherits Component
        <DllImport("User32.dll")> _
        Private Shared Function GetLastInputInfo(ByRef plii As LASTINPUTINFO) As Boolean
        End Function

#Region "Fields"

        Private _idleThreshold As Long
        Private _lastActivity As UInteger
        Private _timerEnabled As Boolean
        Private _userActiveState As UserActivityState = UserActivityState.Unknown
        Private _lastResetTime As DateTime
        Private _tag As Object

        Private lastInput As LASTINPUTINFO
        Private activityCheckerTimer As Timer
        Private activityStopWatch As New Stopwatch()

#End Region

#Region "Events"
        <Description("Occurs when the user becomes active.")> _
        Public Event UserActiveEvent As EventHandler

        <Description("Occurs when the user becomes idle.")> _
        Public Event UserIdleEvent As EventHandler
#End Region

#Region "Methods"

        ''' <summary>
        ''' Constructs a new timer to track the idle/activity status of the user.
        ''' Defaults to 60-second idle threshold in a disabled state
        ''' </summary>
        Public Sub New()
            Me.New(60000, False)
        End Sub

        ''' <summary>
        ''' Constructs a new timer to track the idle/activity status of the user.
        ''' Defaults to a disabled state.
        ''' </summary>
        ''' <param name="idleThreshold">Number of milliseconds of inactivity until user is considered idle</param>
        Public Sub New(ByVal idleThreshold As Long)
            Me.New(idleThreshold, False)
        End Sub

        ''' <summary>
        ''' Constructs a new timer to track the idle/activity status of the user
        ''' </summary>
        ''' <param name="idleThreshold">Number of milliseconds of inactivity until user is considered idle</param>
        ''' <param name="active">Whether or not the timer should be created in an active state</param>
        Public Sub New(ByVal idleThreshold As Long, ByVal active As Boolean)
            Me._idleThreshold = idleThreshold

            ' Initialize system call
            Me.lastInput = New LASTINPUTINFO()
            Me.lastInput.cbSize = CInt(Marshal.SizeOf(Me.lastInput))

            ' Read initial value
            GetLastInputInfo(Me.lastInput)
            Me._lastActivity = Me.lastInput.dwTime

            ' Create underlying timer
            Me.activityCheckerTimer = New Timer(AddressOf Me.GetLastInput, Nothing, Timeout.Infinite, 1000)
            Me._lastResetTime = DateTime.Now
            Me.Enabled = active
        End Sub

        ''' <summary>
        ''' Resets the elapsed activity time to zero and continues measuring.
        ''' </summary>
        Public Sub Reset()
            activityStopWatch.Reset()
            Me._userActiveState = UserActivityState.Unknown
            Me._lastResetTime = DateTime.Now

            If Me._timerEnabled Then
                activityStopWatch.Start()
            End If
        End Sub

        ''' <summary>
        ''' Disables the activity timer and associated events
        ''' </summary>
        Public Sub Disable()
            ' Ignore if already disabled
            If Me._timerEnabled Then
                Me.activityCheckerTimer.Change(Timeout.Infinite, Timeout.Infinite)
                Me._timerEnabled = False
                Me._userActiveState = UserActivityState.Unknown
                Me.activityStopWatch.Stop()
            End If
        End Sub

        ''' <summary>
        ''' Enables the activity timer and associated events
        ''' </summary>
        Public Sub Enable()
            ' Ignore if already enabled
            If Not Me._timerEnabled Then
                Me.activityCheckerTimer.Change(0, 1000)
                Me._timerEnabled = True
                Me.activityStopWatch.Start()
            End If
        End Sub

        ''' <summary>
        ''' Checks to see the last activity timestamp and compares is to the idle threshold.
        ''' Will trigger appropriate events if state changes.
        ''' </summary>
        ''' <param name="userState">Ignored - required for ThreadStart delegate</param>
        Private Sub GetLastInput(ByVal userState As Object)
            GetLastInputInfo(Me.lastInput)
            Me._lastActivity = Me.lastInput.dwTime

            If (Environment.TickCount - Me.lastInput.dwTime) > Me._idleThreshold Then
                If Me._userActiveState <> UserActivityState.Inactive Then
                    Me._userActiveState = UserActivityState.Inactive
                    Me.activityStopWatch.Stop()
                    Me.RaiseUserIdleEvent()
                End If
            ElseIf Me._userActiveState <> UserActivityState.Active Then
                Me._userActiveState = UserActivityState.Active
                Me.activityStopWatch.Start()
                Me.RaiseUserActiveEvent()
            End If
        End Sub

#End Region

#Region "Events"
        ''' <summary>
        ''' Performs appropriate error-checking before raising the UserActive event
        ''' </summary>
        Private Sub RaiseUserActiveEvent()
            RaiseEvent UserActiveEvent(Me, Nothing)
        End Sub

        ''' <summary>
        ''' Performs appropriate error-checking before raising the UserIdle event
        ''' </summary>
        Private Sub RaiseUserIdleEvent()
            RaiseEvent UserIdleEvent(Me, Nothing)
        End Sub
#End Region

#Region "Properties"

        ''' <summary>
        ''' Gets the time the timer was created or last reset.
        ''' </summary>
        <Browsable(False)> _
        <Description("The time the timer was created or last reset.")> _
        Public ReadOnly Property LastResetTime() As DateTime
            Get
                Return _lastResetTime
            End Get
        End Property

        ''' <summary>
        ''' Gets or sets the amount of inactivity until user is considered idle, measured in milliseconds.
        ''' </summary>
        <DefaultValue(60000)> _
        <Category("Behavior")> _
        <Description("The amount of inactivity until user is considered idle, measured in milliseconds.")> _
        Public Property IdleThreshold() As Long
            Get
                Return Me._idleThreshold
            End Get
            Set(ByVal value As Long)
                Me._idleThreshold = value
            End Set
        End Property

        ''' <summary>
        ''' Gets the total elapsed time that the user has been active, in milliseconds.
        ''' </summary>
        <Browsable(False)> _
        Public ReadOnly Property ActiveTimeMilliseconds() As Long
            Get
                Return Me.activityStopWatch.ElapsedMilliseconds
            End Get
        End Property

        ''' <summary>
        ''' Gets the total elapsed time that the user has been active.
        ''' </summary>
        <Browsable(False)> _
        Public ReadOnly Property ActiveTime() As TimeSpan
            Get
                Return Me.activityStopWatch.Elapsed
            End Get
        End Property

        ''' <summary>
        ''' Gets or sets the current state of the timer
        ''' </summary>
        <DefaultValue(False)> _
        <Category("Behavior")> _
        <Description("The current state of the timer.")> _
        Public Property Enabled() As Boolean
            Get
                Return Me._timerEnabled
            End Get
            Set(ByVal value As Boolean)
                ' If in design-time change the value but not the actual state
                If Not Me.DesignMode Then
                    If value = True Then
                        Enable()
                    Else
                        Disable()
                    End If
                Else
                    Me._timerEnabled = value
                End If
            End Set
        End Property

        ''' <summary>
        ''' Gets the current state of the user
        ''' </summary>
        <Browsable(False)> _
        Public ReadOnly Property UserActiveState() As UserActivityState
            Get
                Return Me._userActiveState
            End Get
        End Property

        ''' <summary>
        '''  Gets or sets an arbitrary string representing some type of user state.
        ''' </summary>
        <TypeConverter(GetType(StringConverter))> _
        <DefaultValue("")> _
        <Localizable(False)> _
        <Description("User-defined data associated with the object.")> _
        <Category("Data")> _
        Public Property Tag() As Object
            Get
                Return Me._tag
            End Get
            Set(ByVal value As Object)
                Me._tag = value
            End Set
        End Property
#End Region

#Region "Nested Types"

        <StructLayout(LayoutKind.Sequential)> _
        Friend Structure LASTINPUTINFO
            Public cbSize As UInteger
            Public dwTime As UInteger
        End Structure

#End Region
    End Class
End Namespace
